home *** CD-ROM | disk | FTP | other *** search
/ Celestin Apprentice 5 / Apprentice-Release5.iso / Source Code / Libraries / VideoToolbox 96.06.15 / VideoToolboxSources / GDTime.c < prev    next >
Text File  |  1996-03-05  |  18KB  |  471 lines

  1. /*
  2. GDTime.c
  3.  
  4. All these routines measure the timing of some aspect of the &unction of a video
  5. device. For background, read “Video synch” and run TimeVideo, which reports the
  6. results of running all these routines.
  7.  
  8. double GDFrameRate(GDHandle device) measures the frame rate of a video device in
  9. Hz. (A NULL “device” argument to this routine, or any of the routines below,
  10. requests use of the System VBL interrupt, which normally runs at 60.15 Hz.) It
  11. times by counting VBL interrupts (discarding any spurious ones, to deal with the
  12. problem described in the next paragraph).
  13.  
  14. double GDVBLRate(GDHandle device) measures the rate of VBL interrupts generated
  15. by a video device in Hz. According to Apple’s Designing Cards and Drivers book
  16. and other documentation, the video driver and card are supposed to generate one
  17. VBL interrupt per frame. However, many don’t. E.g. Apple’s 4•8 and 8•24 video
  18. cards issue several interrupts per frame. Read the “Video synch” file.
  19.  
  20. double GDMovieSize(GDHandle device,int quickly) measures what fraction of the
  21. screen you can fill with a real-time movie (a new image on each frame), using
  22. CopyBitsQuickly (if quickly!=0) or CopyBits (if quickly==0) to copy from memory
  23. to video card. At one time CopyBitsQuickly() was much faster than CopyBits(),
  24. but my latest measurements, using GDMovieRate, indicate that there is no longer
  25. any difference in speed. However, CopyBitsQuickly ignores the color tables and
  26. CopyBits uses them. Of course, when you’re showing movies you don’t want to
  27. waste time with color tables, so GDMovieRate() makes a PixMap that shares the
  28. device’s color table. For reasons that I don’t understand even in that case
  29. using CopyBits to copy from, and then back to, the screen doesn’t always preserve
  30. the original colors.
  31.  
  32. double GDMovieRate(GDHandle device,int quickly) measures the rate (images/s) at
  33. which you can show a full-screen movie.
  34.  
  35. error=GDTimeClut(device,GDSetEntries,clutEntries,&s,&frames,&missingFrames,&frameRate);
  36. measures how long it takes to load the clut. It measures in two kinds of units
  37. simultaneously, frames and seconds. You supply the function to be tested, e.g.
  38. SetEntriesQuickly or GDSetEntries. (If gdType==directType GDTimeClut will
  39. automatically substitute GDDirectSetEntries for GDSetEntries.) The second
  40. argument, “clutEntries”, specifies how many clut entries you want to update each
  41. time, or zero for all. GDTimeClut also measures the frame rate independently,
  42. which it returns, after using it to estimate how many frame interrupts were
  43. missed during each clut load. If there’s at least one frame missing or if the
  44. frame count is very small, less than 0.5 per call, then it estimates the frames
  45. directly from the time. You may substitute NULL for any of the pointer-to-double
  46. arguments.
  47.  
  48. NOTES:
  49. It is of interest to time GDSetEntries (and its sibling GDDirectSetEntries) at
  50. both normal (zero) and high (7) processor interrupt priority, because some
  51. drivers are asynchronous when run at low priority, returning immediately and
  52. deferring the actual clut loading until the vbl interrupt occurs, but Apple
  53. specifies that all drivers must be synchronous when run at high priority. You do
  54. this by supplying the new routine GDSetEntriesByTypeHighPriority as an argument
  55. to GDTimeClut.
  56.  
  57. Similarly, while the problem of multiple interrupts per frame is dealt with
  58. satisfactorily by VBLInstall.c, using the scheme suggested by Raynald Comtois,
  59. it is of technical interest to follow up the report that there are no extra
  60. interrupts if the processor instruction cache is disabled. One theory to account
  61. for this is that perhaps disabling the cache causes the interrupt service
  62. routine to take long enough that the hardware interrupt pulse has terminated
  63. before the routine exits. Thus I would like to write a routine called
  64. GDSetEntriesNoCache, but I don’t know how to disable the cache.
  65.  
  66. HISTORY:
  67. 8/22/92 dgp    wrote ‘em.
  68. 8/26/92    dgp    added clutEntries argument
  69. 8/28/92    dgp    updated to use new reentrant Timer.c
  70. 9/11/92    dgp enhanced GDFrameRate() to discard bogus VBL interrupts and to return
  71.             true frame rate, as suggested by Raynald Comtois. Added GDVBLRate(), 
  72.             which corresponds to the old GDFrameRate().
  73. 9/15/92    dgp    GDMovieRate() now asks the video driver what mode we’re in, just in case
  74.             QuickDraw’s been fooled.
  75. 9/17/92    dgp    Added second argument to GDMovieRate() to select CopyBitsQuickly vs CopyBits.
  76. 10/5/92    dgp    fixed bug in GDMovieRate() that caused crashes or noop when testing other 
  77.             than the main device.
  78. 10/9/92    dgp    now actually initialize the linearTable for direct clut.
  79. 10/10/92 dgp Squeezed out extra space from rowBytes in GDMovieRate() so as not to show
  80.             garbage. Now use Temporary memory if there isn’t enough memory in the application 
  81.             heap to show a full-screen movie. Show movie for 3 seconds. 
  82. 10/13/92 dgp In response to a report from Tom Busey, that frames were going uncounted
  83.             during the clut timing, which seems to be a problem with some video drivers,
  84.             GDFramesPerClutUpdate() now double checks the timing in secs, and if
  85.             it finds a discrepancy, prints a warning to the screen and reports a
  86.             corrected values based on the timing in secs. This should be more reliable.
  87. 10/13/92 dgp Fixed error in printf in GDFrameRate().
  88. 11/23/92 dgp Set nominalBits equal to pixelSize instead of Log2L(ctsize).
  89. 12/30/92 dgp Commented out warning from GDFramesPerClutUpdate().
  90.              •Check for cscSetEntries error in the clut timing routines, and return
  91.              NAN in that case. •Use trial and error to determine clut size.
  92. 1/4/92    dgp    GDTimeClutUpdate now returns NAN on GDSetEntries error.
  93. 1/6/92    dgp    Fixed computation of linearTable, so CLUT is preserved by GDTimeClutUpdate.
  94. 1/11/93    dgp    Enhanced GDMovieRate() to work even when Color QuickDraw is absent, by 
  95.             calling the new routine GDMovieRateNoColorQuickDraw().
  96.             Check returned Ptr from NewTimer() to make sure it’s ok; will be NULL
  97.             if computer only has Standard Time Manager.
  98. 1/24/93    dgp    Reduced timing interval from 3 to 1 s for movies.
  99. 3/11/93    dgp created GDTimeClut, based on GDTimeClutUpdate and
  100.             GDFramesPerClutUpdate. The enhancements are 1. you
  101.             supply the function to be tested, e.g. SetEntriesQuickly
  102.             or GDSetEntries. 2. It doesn’t print or exit, always
  103.             returning with an informative OSErr. 3. It measures frame
  104.             rate independently, which it returns, and also uses it to
  105.             estimate how many frame interrupts were missed during
  106.             each clut load. 4. If there’s at least one frame missing
  107.             or if the frame count is very small, less than 0.5 per
  108.             call, then it estimates the frames directly from the
  109.             time.
  110. 3/15/93    dgp    Fixed portRect clipping error in GDMovieRateNoColorQuickDraw.
  111. 4/17/93    dgp    Merged GDFrameRate.c and GDTimeClut.c to produce GDTime.c
  112. 4/19/93    dgp    Fixed bug in GDTimeClut that used garbage color table in place of
  113.             linear color table. Now uses GDNewLinearColorTable.
  114. 4/25/93    dgp    Changed struct from static to automatic.
  115. 5/31/94    dgp    Made compatible with Apple's Universal Headers. Replaced MBarHeight by
  116.             GetMBarHeight() and added calls to NewVBLProc and DisposeRoutineDescriptor.
  117. 9/5/94 dgp removed assumption in printf's that int==short.
  118. */
  119. #include "VideoToolbox.h"
  120. #if UNIVERSAL_HEADERS
  121.     #include <LowMem.h>
  122. #else
  123.     #define LMGetMBarHeight() (* (short *) 0x0BAA)
  124.     #define LMSetMBarHeight(MBarHeightValue) ((* (short *) 0x0BAA) = (MBarHeightValue))
  125. #endif
  126. #if !UNIVERSAL_HEADERS
  127.     typedef ProcPtr UniversalProcPtr;
  128.     #define NewVBLProc(userRoutine) (UniversalProcPtr)(userRoutine)
  129.     #define DisposeRoutineDescriptor(userRoutine) 
  130. #endif
  131. double GDMovieRateNoColorQuickDraw(int quickly);
  132. // Original typedef is in VideoToolbox.h
  133. //typedef OSErr (*SetEntriesFunction)(GDHandle device,short start,short count
  134. //    ,ColorSpec *aTable);
  135. #define CALLS 30            // fewer for speed, more for accuracy
  136. #define FRAMES 10            // fewer for speed, more for accuracy
  137. #define SHOW_MOVIE_WINDOW 0    // A matter of taste, but I prefer not to show it.
  138. #define SECONDS 0.5            // Movie duration.
  139.  
  140. OSErr GDTimeClut(GDHandle device,SetEntriesFunction function,short clutEntries
  141.     ,double *sPtr,double *framesPtr,double *missingFramesPtr,double *frameRatePtr)
  142. {
  143.     int error;
  144.     short clutSize,i;
  145.     ColorSpec *table,*linearTable=NULL;
  146.     VBLTaskAndA5 vblData;
  147.     long frames;
  148.     Timer *timer;
  149.     double s,missingFrames,frameRate;
  150.  
  151.     if(sPtr!=NULL)*sPtr=NAN;
  152.     if(framesPtr!=NULL)*framesPtr=NAN;
  153.     if(missingFramesPtr!=NULL)*missingFramesPtr=NAN;
  154.     if(frameRatePtr!=NULL)*frameRatePtr=NAN;
  155.     if(device==NULL || (*device)->gdType==fixedType){
  156.         if(frameRatePtr!=NULL)*frameRatePtr=GDFrameRate(device);
  157.         return 0;
  158.     }
  159.     clutSize=GDClutSize(device);
  160.     if(clutEntries<0 || clutEntries>clutSize)return 1;
  161.     if(clutEntries==0)clutEntries=clutSize;
  162.     vblData.subroutine=NULL;                        // setup frame counter
  163.     error=VBLInstall(&vblData,device,CALLS*20);        // setup frame counter
  164.     if(error)return error;
  165.     timer=NewTimer();                                // setup timer
  166.     if(timer==NULL)return 1;                        // lacks Revised Time Manager.
  167.     if((*device)->gdType==directType){
  168.         if(function==GDSetEntries)function=GDDirectSetEntries;
  169.         table=linearTable=GDNewLinearColorTable(device);
  170.         if(linearTable==NULL)return MemError();
  171.     }else table=((**(**(**device).gdPMap).pmTable)).ctTable;
  172.     vblData.vbl.vblCount=1;                            // Enable interrupt service routine
  173.     for(i=-1;i<CALLS;i++) {
  174.         error=(function)(device,0,clutEntries-1,table);
  175.         if(i==-1){
  176.             StartTimer(timer);
  177.             frames=vblData.framesLeft;
  178.         }
  179.         if(error)break;
  180.     }
  181.     frames-=vblData.framesLeft;
  182.     s=StopTimerSecs(timer);
  183.     VBLRemove(&vblData);
  184.     DisposeTimer(timer);
  185.     if(linearTable!=NULL)DisposePtr((Ptr)linearTable);
  186.     if(error)return error;
  187.     
  188.     // Estimate number of missing frames by discrepancy between frames and secs.
  189.     frameRate=GDFrameRate(device);
  190.     missingFrames=s*frameRate-frames;
  191.     
  192.     // Return results
  193.     if(sPtr!=NULL)*sPtr=s/CALLS;
  194.     if(framesPtr!=NULL){
  195.         if(fabs(missingFrames)>1. || frames<CALLS/2) *framesPtr=frameRate*s/CALLS;
  196.         else *framesPtr=frames/(double)CALLS;
  197.     }
  198.     if(missingFramesPtr!=NULL)*missingFramesPtr=missingFrames/CALLS;
  199.     if(frameRatePtr!=NULL)*frameRatePtr=frameRate;
  200.     return 0;
  201. }
  202.  
  203. double GDFrameRate(GDHandle device)
  204. {
  205.     VBLTaskAndA5 vblData;
  206.     Timer *timer;
  207.     register long frames;
  208.     int error;
  209.     double s;
  210.     
  211.     timer=NewTimer();
  212.     if(timer==NULL)return NAN;            // lacks Revised Time Manager.
  213.     vblData.subroutine=NULL;
  214.     error=VBLInstall(&vblData,device,FRAMES);
  215.     if(error)PrintfExit("GDFrameRate: VBLInstall: error %d\n",error);
  216.     vblData.vbl.vblCount=1;                // Enable interrupt service routine
  217.     frames=vblData.framesDesired-2;
  218.     while(vblData.framesLeft>frames) ;    // wait for second frame
  219.     StartTimer(timer);
  220.     while(vblData.framesLeft) ;            // wait for last frame
  221.     s=StopTimerSecs(timer);
  222.     VBLRemove(&vblData);
  223.     DisposeTimer(timer);
  224.     return frames/s;
  225. }
  226.  
  227. double GDVBLRate(GDHandle device)
  228. {
  229.     VBLTaskAndA5 vblData;
  230.     Timer *timer;
  231.     register long frames;
  232.     int error;
  233.     double s;
  234.     
  235.     timer=NewTimer();
  236.     if(timer==NULL)return NAN;            // lacks Revised Time Manager.
  237.     vblData.subroutine=(void *)NewVBLProc(SimpleVBLSubroutine);
  238.     error=VBLInstall(&vblData,device,FRAMES);
  239.     if(error)PrintfExit("GDVBLRate: VBLInstall: error %d\n",error);
  240.     vblData.vbl.vblCount=1;                // Enable interrupt service routine
  241.     frames=vblData.framesDesired-2;
  242.     while(vblData.framesLeft>frames) ;    // wait for second frame
  243.     StartTimer(timer);
  244.     while(vblData.framesLeft) ;            // wait for last frame
  245.     s=StopTimerSecs(timer);
  246.     VBLRemove(&vblData);
  247.     DisposeRoutineDescriptor(vblData.subroutine);
  248.     DisposeTimer(timer);
  249.     return frames/s;
  250. }
  251.  
  252. double GDMovieSize(GDHandle device,int quickly)
  253. {
  254.     return GDMovieRate(device,quickly)/GDFrameRate(device);
  255. }
  256.  
  257. double GDMovieRate(GDHandle device,int quickly)
  258. {
  259.     Timer *timer;
  260.     register long image;
  261.     long images;
  262.     int error;
  263.     OSErr osErr;
  264.     double s=NAN,fractionOfFrame=NAN;
  265.     PixMap **pm;
  266.     unsigned long bytes;
  267.     GDHandle oldDevice;
  268.     WindowPtr window,oldPort;
  269.     Rect r,rLocal;
  270.     Ptr oldBaseAddr;
  271.     Handle saveSpace,bufferHandle;
  272.     long osAttr;
  273.     int tempMem;
  274.     
  275.     if(!QD8Exists())return GDMovieRateNoColorQuickDraw(quickly);
  276.     oldDevice=GetGDevice();
  277.     SetGDevice(device);
  278.     pm=NewPixMap();
  279.     SetGDevice(oldDevice);
  280.     if(pm==NULL)goto done0;
  281.     HLock((Handle)pm);
  282.     // The color table is needed for CopyBits(); CopyBitsQuickly doesn't care.
  283.     (**pm).pmTable=(**(**device).gdPMap).pmTable;    // share device's color table
  284.     if(device==GetMainDevice())(**pm).bounds.top+=LMGetMBarHeight();
  285.     if(SHOW_MOVIE_WINDOW){
  286.         (**pm).bounds.top+=19;    // Allow room for window title
  287.         InsetRect(&(**pm).bounds,32,32);
  288.     }
  289.     bufferHandle=NULL;
  290.     bytes=(**pm).bounds.right-(**pm).bounds.left;
  291.     bytes*=(**pm).pixelSize;
  292.     bytes=((bytes+31)/32)*4;    // convert bits to bytes, rounding up to multiple of 4
  293.     (**pm).rowBytes &= ~0x3fff;
  294.     (**pm).rowBytes |= bytes;
  295.     Gestalt(gestaltOSAttr,&osAttr);
  296.     tempMem=osAttr & 1L<<gestaltTempMemSupport;
  297.     while(1){
  298.         bytes=(**pm).rowBytes & 0x3fff;
  299.         bytes*=(**pm).bounds.bottom-(**pm).bounds.top;
  300.         if(bytes==0)goto done1;
  301.         saveSpace=NewHandle(2000);    // save some space
  302.         bufferHandle=NewHandle(bytes+1200);    // extra is for drifting
  303.         if(saveSpace!=NULL)DisposeHandle(saveSpace);
  304.         if(bufferHandle==NULL && tempMem)bufferHandle=TempNewHandle(bytes+1200,&osErr);
  305.         error=osErr;
  306.         if(bufferHandle!=NULL)break;
  307.         // Halve the window's height before trying again
  308.         (**pm).bounds.bottom=(**pm).bounds.top+((**pm).bounds.bottom-(**pm).bounds.top)/2;
  309.     }
  310.     HLock(bufferHandle);
  311.     (**pm).baseAddr=*bufferHandle;
  312.     GetPort(&oldPort);
  313.     r=(**pm).bounds;
  314.     window=NewCWindow(NULL,&r,"\pmovie",0,0,(WindowPtr)-1,0,0);    // don't show it yet
  315.     if(window==NULL)goto done2;
  316.     timer=NewTimer();
  317.     if(timer==NULL)goto done2;        // lacks Revised Time Manager.
  318.     SetPort(window);
  319.     HLock((Handle)((CWindowPtr)window)->portPixMap);
  320.     SetGDevice(device);
  321.     rLocal=r;
  322.     GlobalToLocalRect(&rLocal);
  323.     StartTimer(timer);
  324.     if(quickly)CopyBitsQuickly((BitMap *)*((CWindowPtr)window)->portPixMap,(BitMap *)*pm
  325.         ,&rLocal,&r,srcCopy,NULL);    // copy screen to memory
  326.     else{
  327.         CopyBits((BitMap *)*((CWindowPtr)window)->portPixMap,(BitMap *)*pm
  328.         ,&rLocal,&r,srcCopy,NULL);    // copy screen to memory
  329.         error=QDError();
  330.         if(error){
  331.             printf("GDMovieRate: CopyBits generated QuickDraw error %d\n",error);
  332.             goto done3;
  333.         }
  334.     }
  335.     s=StopTimerSecs(timer);    // rough estimate of time for one image
  336.     if(SHOW_MOVIE_WINDOW || !quickly)ShowWindow(window);// CopyBits won't copy to a hidden window
  337.     StartTimer(timer);
  338.     oldBaseAddr=(**pm).baseAddr;
  339.     images=ceil(SECONDS/s);
  340.     for(image=images;image>0;image--){
  341.         if(image==1)(**pm).baseAddr=oldBaseAddr;
  342.         else (**pm).baseAddr+=4;    // Drift image to prove it's a movie
  343.         // We drift by multiples of 4 bytes because long-aligned copying is faster.
  344.         if(quickly)CopyBitsQuickly((BitMap *)*pm,(BitMap *)*((CWindowPtr)window)->portPixMap
  345.             ,&r,&rLocal,srcCopy,NULL);    // copy memory to screen
  346.         else CopyBits((BitMap *)*pm,(BitMap *)*((CWindowPtr)window)->portPixMap
  347.             ,&r,&rLocal,srcCopy,NULL);    // copy memory to screen
  348.     }
  349.     s=StopTimerSecs(timer);
  350.     fractionOfFrame=(long)(r.bottom-r.top)*(long)(r.right-r.left);
  351.     r=(**(**device).gdPMap).bounds;
  352.     fractionOfFrame/=(long)(r.bottom-r.top)*(long)(r.right-r.left);
  353.     DisposeTimer(timer);
  354. done3:
  355.     SetPort(oldPort);
  356.     DisposeWindow(window);
  357. done2:
  358.     DisposHandle(bufferHandle);
  359. done1:
  360.     (**pm).pmTable=NULL;
  361.     DisposePixMap(pm);
  362. done0:
  363.     SetGDevice(oldDevice);
  364.     return images*fractionOfFrame/s;
  365. }
  366.  
  367. double GDMovieRateNoColorQuickDraw(int quickly)
  368. {
  369.     Timer *timer;
  370.     register long image;
  371.     long images;
  372.     int error=0;
  373.     OSErr osErr;
  374.     double s=NAN,fractionOfFrame=NAN;
  375.     BitMap bitmap;
  376.     unsigned long bytes;
  377.     Rect r;
  378.     Ptr oldBaseAddr;
  379.     Handle saveSpace,bufferHandle=NULL;
  380.     long osAttr;
  381.     int tempMem;
  382.     GrafPort portRec,*port=&portRec,*oldPort;
  383.     
  384.     GetPort(&oldPort);
  385.     OpenPort(port);
  386.     SetPort(port);
  387.     bitmap=port->portBits;
  388.     bytes=bitmap.bounds.right-bitmap.bounds.left;
  389.     bitmap.rowBytes=((bytes+31)/32)*4;    // convert bits to bytes, round up to mult of 4
  390.     Gestalt(gestaltOSAttr,&osAttr);
  391.     tempMem=osAttr & 1L<<gestaltTempMemSupport;
  392.     while(1){
  393.         bytes=bitmap.rowBytes & 0x3fff;
  394.         bytes*=bitmap.bounds.bottom-bitmap.bounds.top;
  395.         if(bytes==0)goto done;
  396.         saveSpace=NewHandle(2000);    // save some space
  397.         bufferHandle=NewHandle(bytes+600);    // extra is for drifting
  398.         if(saveSpace!=NULL)DisposeHandle(saveSpace);
  399.         if(bufferHandle==NULL && tempMem)bufferHandle=TempNewHandle(bytes+600,&osErr);
  400.         error=osErr;
  401.         if(bufferHandle!=NULL)break;
  402.         // Halve the window's height before trying again
  403.         bitmap.bounds.bottom=bitmap.bounds.top+(bitmap.bounds.bottom-bitmap.bounds.top)/2;
  404.     }
  405.     HLock(bufferHandle);
  406.     bitmap.baseAddr=*bufferHandle;
  407.     timer=NewTimer();
  408.     if(timer==NULL)goto done;    // lacks Revised Time Manager.
  409.     r=bitmap.bounds;
  410.     StartTimer(timer);
  411.     if(quickly)CopyBitsQuickly(&port->portBits,&bitmap
  412.         ,&r,&r,srcCopy,NULL);    // copy screen to memory
  413.     else CopyBits(&port->portBits,&bitmap
  414.         ,&r,&r,srcCopy,NULL);    // copy screen to memory
  415.     s=StopTimerSecs(timer);        // approximate time for one image
  416.     error=QDError();
  417.     if(!quickly && error){
  418.         printf("GDMovieRate: CopyBits generated QuickDraw error %d\n",error);
  419.         DisposeTimer(timer);
  420.         goto done;
  421.     }
  422.     oldBaseAddr=bitmap.baseAddr;
  423.     images=ceil(SECONDS/s);    // Let's time for this many seconds.
  424.     StartTimer(timer);
  425.     for(image=images;image>0;image--){
  426.         if(image==1)bitmap.baseAddr=oldBaseAddr;
  427.         else bitmap.baseAddr+=2;    // Drift the image, to prove it's a movie
  428.         if(quickly)CopyBitsQuickly(&bitmap,&port->portBits
  429.             ,&r,&r,srcCopy,NULL);    // copy memory to screen
  430.         else CopyBits(&bitmap,&port->portBits
  431.             ,&r,&r,srcCopy,NULL);    // copy memory to screen
  432.     }
  433.     s=StopTimerSecs(timer);
  434.     DisposeTimer(timer);
  435.     error=QDError();
  436.     if(!quickly && error){
  437.         printf("GDMovieRate: CopyBits generated QuickDraw error %d\n",error);
  438.         DisposeTimer(timer);
  439.         goto done;
  440.     }
  441.     fractionOfFrame=(long)(r.bottom-r.top)*(long)(r.right-r.left);
  442.     r=port->portBits.bounds;
  443.     fractionOfFrame/=(long)(r.bottom-r.top)*(long)(r.right-r.left);
  444. done:
  445.     SetPort(oldPort);
  446.     ClosePort(port);
  447.     if(bufferHandle==NULL){
  448.         printf("GDMovieRate: Not enough memory!\n");
  449.         return NAN;
  450.     }
  451.     DisposeHandle(bufferHandle);
  452.     return images*fractionOfFrame/s;
  453. }
  454.  
  455. double TickRate(void)
  456. {
  457.     Timer *timer;
  458.     double s;
  459.     long t;
  460.     
  461.     timer=NewTimer();
  462.     if(timer==NULL)return NAN;            // lacks Revised Time Manager.
  463.     Delay(1,&t);
  464.     StartTimer(timer);
  465.     Delay(FRAMES,&t);
  466.     s=StopTimerSecs(timer);
  467.     DisposeTimer(timer);
  468.     return FRAMES/s;
  469. }
  470.  
  471.